package top.laoshuzi.rxmvp.presenter;

import android.support.annotation.Nullable;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;

import io.reactivex.Observable;
import io.reactivex.disposables.CompositeDisposable;
import io.reactivex.disposables.Disposable;
import io.reactivex.functions.BiConsumer;
import io.reactivex.functions.Consumer;
import io.reactivex.subjects.BehaviorSubject;
import top.laoshuzi.android.mvp.presenter.Presenter;
import top.laoshuzi.rxmvp.presenter.delivery.DeliverFirst;
import top.laoshuzi.rxmvp.presenter.delivery.DeliverLatestCache;
import top.laoshuzi.rxmvp.presenter.delivery.DeliverReplay;
import top.laoshuzi.rxmvp.presenter.delivery.Delivery;
import top.laoshuzi.rxmvp.view.OptionalView;

public class RxPresenter<View> extends Presenter<View> {

    private final CompositeDisposable disposables = new CompositeDisposable();
    private final BehaviorSubject<OptionalView<View>> views = BehaviorSubject.create();

    private final HashMap<Integer, Factory<Disposable>> restartables = new HashMap<>();
    private final HashMap<Integer, Disposable> restartableDisposables = new HashMap<>();
    private final ArrayList<Integer> requested = new ArrayList<>();

    @Override
    public void onCreate() {
        super.onCreate();
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        views.onComplete();
        disposables.dispose();
        for (Map.Entry<Integer, Disposable> entry : restartableDisposables.entrySet())
            entry.getValue().dispose();
    }

    public void add(Disposable disposable) {
        this.disposables.add(disposable);
    }

    public void remove(Disposable disposable) {
        this.disposables.remove(disposable);
    }


    public void restartable(int restartableId, Factory<Disposable> factory) {
        restartables.put(restartableId, factory);
        if (requested.contains(restartableId))
            start(restartableId);
    }

    public void start(int restartableId) {
        stop(restartableId);
        requested.add(restartableId);
        restartableDisposables.put(restartableId, restartables.get(restartableId).create());
    }

    public void stop(int restartableId) {
        requested.remove((Integer) restartableId);
        Disposable disposable = restartableDisposables.get(restartableId);
        if (disposable != null)
            disposable.dispose();
    }

    public boolean isDisposed(int restartableId) {
        Disposable disposable = restartableDisposables.get(restartableId);
        return disposable == null || disposable.isDisposed();
    }

    //

    public <T> void restartableFirst(int restartableId,
                                     final Factory<Observable<T>> observableFactory,
                                     final BiConsumer<View, T> onNext) {
        restartableFirst(restartableId, observableFactory, onNext, null);
    }

    public <T> void restartableFirst(int restartableId,
                                     final Factory<Observable<T>> observableFactory,
                                     final BiConsumer<View, T> onNext,
                                     @Nullable final BiConsumer<View, Throwable> onError) {
        restartable(restartableId, new Factory<Disposable>() {
            @Override
            public Disposable create() {
                return observableFactory.create()
                        .compose(RxPresenter.this.<T>deliverFirst())
                        .subscribe(split(onNext, onError));
            }
        });
    }

    public <T> void restartableLatestCache(int restartableId,
                                           final Factory<Observable<T>> observableFactory,
                                           final BiConsumer<View, T> onNext) {
        restartableLatestCache(restartableId, observableFactory, onNext, null);
    }

    public <T> void restartableLatestCache(int restartableId, final Factory<Observable<T>> observableFactory,
                                           final BiConsumer<View, T> onNext,
                                           @Nullable final BiConsumer<View, Throwable> onError) {
        restartable(restartableId, new Factory<Disposable>() {
            @Override
            public Disposable create() {
                return observableFactory.create()
                        .compose(RxPresenter.this.<T>deliverLatestCache())
                        .subscribe(split(onNext, onError));
            }
        });
    }

    public <T> void restartableReplay(int restartableId,
                                      final Factory<Observable<T>> observableFactory,
                                      final BiConsumer<View, T> onNext) {
        restartableReplay(restartableId, observableFactory, onNext, null);
    }

    public <T> void restartableReplay(int restartableId,
                                      final Factory<Observable<T>> observableFactory,
                                      final BiConsumer<View, T> onNext,
                                      @Nullable final BiConsumer<View, Throwable> onError) {
        restartable(restartableId, new Factory<Disposable>() {
            @Override
            public Disposable create() {
                return observableFactory.create()
                        .compose(RxPresenter.this.<T>deliverReplay())
                        .subscribe(split(onNext, onError));
            }
        });
    }

    public Observable<OptionalView<View>> view() {
        return views;
    }

    public <T> DeliverFirst<View, T> deliverFirst() {
        return new DeliverFirst<>(views);
    }

    public <T> DeliverLatestCache<View, T> deliverLatestCache() {
        return new DeliverLatestCache<>(views);
    }

    public <T> DeliverReplay<View, T> deliverReplay() {
        return new DeliverReplay<>(views);
    }


    public <T> Consumer<Delivery<View, T>> split(BiConsumer<View, T> onNext) {
        return split(onNext, null);
    }

    public <T> Consumer<Delivery<View, T>> split(final BiConsumer<View, T> onNext,
                                                 @Nullable final BiConsumer<View, Throwable> onError) {
        return new Consumer<Delivery<View, T>>() {
            @Override
            public void accept(Delivery<View, T> delivery) throws Exception {
                delivery.split(onNext, onError);
            }
        };
    }

}
